
#include "simulator.h"
#include "busRequest.h"

#include <queue>
#include <string.h>
#include <iostream>
#include <iomanip>

using namespace std;

Simulator::Simulator (int associativity, int blockSizeInBytes, int cacheSizeInBytes) {
  int  i = 0;
  while (i < 4) {
    (this->cacheControllerVector).push_back(new CacheController(associativity, blockSizeInBytes, cacheSizeInBytes));
    i++;
  }
  busController = new BusController();
}


/**
 * Converts a given address in Hexadecimal notation to (tag, set, byte) representation
 * ---------------------------
 * |Tag-20  | Set-6  | Byte-6|  <<-- Address
 * ---------------------------
 */
void Simulator::convertAddress (char * addressString, int& tag, int& set, int& block) {
  unsigned int  addressInt;
  
  //converting from the hex string to int first
  if (addressString != NULL)
    sscanf(addressString, "%x",  &addressInt);
  else
    printf("empty string is passed as input to convert address\n");

  //extracting tag, set, block
  tag = (addressInt & (1048575 << 12)) >> 12;
  set = (addressInt & (63 << 6)) >> 6;
  block = (addressInt & 63);
}


/**
 * Simulates the instructions given in file _filename
 */
void Simulator::simulate (string filename) {
  ifstream inputfile;
  int filePosition;

  string line;
  queue<BusRequest *> reqQueue;
  int t = 0;
  int i = 0;
  int tFlag = 0;

  int timeStamp;
  int cacheId;
  bool readWrite;
  char hexAddr[8];   // 32 bit address, 8 hex digits
  int tag;
  int set_1;
  int byte;

  inputfile.open(filename.c_str());
  if (!inputfile.is_open()) {
    cout << "File " << filename << " could not be opened. Aborting\n";
    return;
  }

  while (inputfile.good()) {      
    tFlag = 0;
    
    /*set tFlag when the requests start arriving with higher time stamp than t
     */
    // peek the next processor request and check if timestamp is more than current timestamp
    // update timestamp by 1 unit in every loop, instead of jumping to next timestamp
    while (tFlag == 0){
      /*reading a line from the file and fragmenting it into relevant field 
	based upon the delimiter blank space..the requests are to be pushed 
	on a queue
      */
      filePosition = inputfile.tellg();
      if (filePosition == -1) {
	break;
      }
      inputfile >> timeStamp;
      if (inputfile.eof()) {
	break;
      }
      if (timeStamp > t) {
	tFlag = 1;
	inputfile.seekg(filePosition, ios::beg);
	break;
      } else {
	inputfile >> cacheId;
	inputfile >> readWrite;
	inputfile >> hexAddr;

	convertAddress(hexAddr, tag, set_1, byte);
	// cout << timeStamp << ' ' << cacheId << ' ' << readWrite << ' ' << tag << ' ' << set_1 << ' ' << byte << '\n';

	/* push the new request on the request queue*/
	BusRequest * newRequest = new BusRequest(timeStamp, cacheId, readWrite, set_1, tag, byte);
	if (readWrite == 0){
	  // read request
	  if((this->cacheControllerVector[cacheId])->serveProcessorReadRequest(newRequest->address) == false) {
	    (this->busController)->requestQueue.push(newRequest);
	  }
	} else {
	  // write request, cannot invalidate other caches here if write miss occurs
	  bool status = (this->cacheControllerVector[cacheId])->serveProcessorWriteRequest(newRequest->address);
	  if (status) {
	    // write hit, invalidate all other caches
	    for (i=0; i < 4; i++) {
	      if (i != cacheId) {
		(this->cacheControllerVector[i])->invalidateData(newRequest->address);
	      }
	    }
	  } else {
	    // write miss
	    (this->busController)->requestQueue.push(newRequest);
	  }
	}
      }
    } 

    /*servicing the top most request on the request queue*/
    if ((this->busController)->requestQueue.size() > 0){
      this->serviceRequest((this->busController)->requestQueue.front());
      (this->busController)->requestQueue.pop();
    }
    t++;  // increase the simulator's timestamp
  }
  inputfile.close();

  // service the remaining requests residing in the busController.
  while ( (this->busController)->requestQueue.size() > 0){
    this->serviceRequest((this->busController)->requestQueue.front());
    (this->busController)->requestQueue.pop();
  }

  // Output the cache coherence statistics
  for (i = 0; i < 4; i++){   
    cout << i << ' ' << this->cacheControllerVector[i]->getCoherenceMissCount() << '\n';
    this->cacheControllerVector[i]->printStateChanges();
    cout << "\n\n";
  }
}
 

/**
 * Service a Bus Request generated by some cache controller
 * to complete Processor Read/Write Request.
 */
void Simulator::serviceRequest(BusRequest* r){
  int cacheId = r->cacheControllerId;
  Block block;
  int data;
  int  i = 0;
  int cacheFlag = 0;

  // branching on the basis of whether it is a write request or a read request
  if (r->rW == 0){
    // service read request
    // service the miss...first check in all other caches in priority based on id 
    for (i=0; i < 4; i++) {
      // State transition to S in all other caches in which data is present
      if (i != cacheId) {
	if ((this->cacheControllerVector[i])->readRequestFromBus(r->address, data, false)){
	  cacheFlag = 1;
	}
      }
    }

    if (cacheFlag == 1) {
      (this->cacheControllerVector[cacheId])->writeRequestFromBus(r->address, true, data, block, false);
    } else if (cacheFlag == 0){
      // miss in all the caches...service from memory
      (this->cacheControllerVector[cacheId])->writeRequestFromBus(r->address, false, data, block, false);
    }
  } else {
    // service write request
    // check in other caches if data is present
    for (i=0; i < 4; i++) {
      if (i != cacheId) {
	if ((this->cacheControllerVector[i])->readRequestFromBus(r->address, data, true)){
	  cacheFlag = 1;
	  (this->cacheControllerVector[cacheId])->writeRequestFromBus(r->address, true, data, block, true);
	  break;
	}
      }
    }

    if (cacheFlag == 1){
      // invalidate other caches data
      for (i=0; i < 4; i++) {
	if (i != cacheId)
	  (this->cacheControllerVector[i])->invalidateData(r->address);
      }
    } else{
      // miss in all other caches also
      // get data from memory and write in _cacheId
      (this->cacheControllerVector[cacheId])->writeRequestFromBus(r->address, 0, data, block, true);
    }
  }
}
